﻿using System;
using System.Collections.Generic;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using System.Threading;
using DotNet.DevOps.Tasks.Util;

namespace DotNet.DevOps.Tasks.Service.Helpers
{
    internal class ServiceControl
    {
        public int CommandTimeout { get; set; }

        public string Machine { get; set; }

        public string Name { get; set; }

        private ILog Log { get; set; }

        private ServiceController controller;
        private ServiceController Controller
        {
            get { return controller ?? (controller = new ServiceController(Name, Machine)); }
        }

        private readonly Regex disabledRegex = new Regex(@"^.*START_TYPE.*\d.*\sDISABLED\s.*$", RegexOptions.Compiled | RegexOptions.Multiline);


        public ServiceControl(ILog log)
        {
            CommandTimeout = -1;
            Name = "";
            Log = log;
        }

        public void Stop()
        {
            RequestState(ServiceControllerStatus.Stopped, Controller.Stop);
        }

        public void Start()
        {
            RequestState(ServiceControllerStatus.Running, Controller.Start);
        }

        private void RequestState(ServiceControllerStatus status, Action action)
        {
            if (CheckControllerIsValid() == true &&
                CheckServiceIsNotDisabled() == true &&
                HasState(status) == false)
            {
                Log.LogInfo(string.Format("{0} - Requesting state: {1}", Name, status));

                WaitForPendingStatusToEnd();

                action.Invoke();

                WaitForStatus(status);
            }
        }

        private bool CheckControllerIsValid()
        {
            try
            {
                Controller.Refresh();
                var status = Controller.Status;
                return true;
            }
            catch (Exception ex)
            {
                var msg = string.Format("{0}", ex.Message);

                throw new Exception(msg);
            }
        }

        private bool HasState(ServiceControllerStatus status)
        {
            if (Controller.Status != status)
            {
                Log.LogInfo(string.Format("{0} - State: {1}", Name, Controller.Status));
                return false;
            }

            Log.LogInfo(string.Format("{0} - Allready in state: {1}.", Name, status));
            return true;
        }

        private void WaitForStatus(ServiceControllerStatus status)
        {
            Controller.Refresh();

            Thread.Sleep(100);

            var whenToStopWaiting = DateTime.Now.AddMilliseconds(CommandTimeout);
            while (ValidStateHaveNotBeenReached(status, whenToStopWaiting))
            {
                Thread.Sleep(100);
                Controller.Refresh();
            }

            Log.LogInfo(string.Format("{0} - State: {1}", Name, Controller.Status));
        }

        private void WaitForPendingStatusToEnd()
        {
            var status = Controller.Status;
            if(status.ToString().ToLower().Contains("pending"))
            {
                var nextStatus = GetNextStatus(status);
                Log.LogInfo(string.Format("{0} - Waiting for state: {1} to change to {2}.", Name, status, nextStatus));

                WaitForStatus(nextStatus);
            }
        }

        private ServiceControllerStatus GetNextStatus(ServiceControllerStatus status)
        {
            if(status == ServiceControllerStatus.ContinuePending || status == ServiceControllerStatus.StartPending )
            {
                return ServiceControllerStatus.Running;
            }

            if(status == ServiceControllerStatus.PausePending)
            {
                return ServiceControllerStatus.Paused;
            }

            if(status == ServiceControllerStatus.StopPending)
            {
                return ServiceControllerStatus.Stopped;
            }

            return status;
        }

        private bool ValidStateHaveNotBeenReached(ServiceControllerStatus expectedState, DateTime whenToStopWaiting)
        {
            CheckForTimeout(expectedState, whenToStopWaiting);

            CheckForUnexpectedStatus(expectedState);

            CheckServiceIsNotDisabled();

            return (Controller.Status == expectedState ? false : true);
        }

        private void CheckForTimeout(ServiceControllerStatus expectedState, DateTime whenToStopWaiting)
        {
            if (CommandTimeout > 0 && DateTime.Now > whenToStopWaiting)
            {
                throw new Exception(string.Format("Timeout has occoured waiting for service to reach state {0}. Last state was {1}.", expectedState, Controller.Status));
            }
        }

        private void CheckForUnexpectedStatus(ServiceControllerStatus expectedState)
        {
            var badStates = new List<ServiceControllerStatus>() 
                                { 
                                    ServiceControllerStatus.Paused, 
                                    ServiceControllerStatus.Stopped,
                                    ServiceControllerStatus.Running
                                };

            badStates.Remove(expectedState);

            if( badStates.Contains(Controller.Status) )
            {
                throw new Exception(string.Format("Unexpectedly ended in state {0} when state {1} was requested.", Controller.Status, expectedState));
            }
        }

        private bool CheckServiceIsNotDisabled()
        {
            var arguments = string.Format(@"\\{0} qc {1} 2048", Machine, Name);

            var output = new Command("sc.exe", arguments, -1).Run();

            var matches = disabledRegex.Matches(output);

            if(matches.Count > 0)
            {
                throw new Exception(string.Format("Startup type is DISABLED and the service will therefore never be able to change status. Possible reasons:\r\n       * The service might have been marked for deletion (check that you don't have any services.msc windows open).\r\n       * The service might have been set to DISABLED on purpose.\r\n       * The service might have security issues and therefore forced into DISABLED startup by the system (check your logs to solve this problem)."));
            }

            return true;
        }
    }
}
